
// INCLUDES ********************************************************************

#include "Drawable.h"

#include "Rectangle.h"

#include "Logger.h"

#include "Timer.h"

#include "Function.h"

#include "Datatypes.h"

#include <OpenGLES/ES1/gl.h>

namespace
{
    //! maximum amount of animations per sprite at a given time
    const unsigned int gMaxAnimations = 5;
}

// NAMESPACES ******************************************************************

#pragma mark -
#pragma mark Constructor
// -----------------------------------------------------------------------------

Meteor::Drawable::Drawable()

: mParent( nullptr), mPosition( 0.0f, 0.0f), mAnchorPoint( 0.5f, 0.5f),
  mSize( 0.0f, 0.0f), mTranslation( 0.0f, 0.0f), mScale ( 1.0f), mIndexZ( 0.0f),
  mRotation( 0.0f), mAlpha( 1.0f), mVisible( true), mTweens()
{
}

// -----------------------------------------------------------------------------

Meteor::Drawable::Drawable( const uint32_t zIndex)

: mParent( nullptr), mPosition( 0.0f, 0.0f), mAnchorPoint( 0.5f, 0.5f),
  mSize( 0.0f, 0.0f), mTranslation( 0.0f, 0.0f), mScale ( 1.0f), mIndexZ( zIndex),
  mRotation( 0.0f), mAlpha( 1.0f), mVisible( true), mTweens()
{
}

// -----------------------------------------------------------------------------

Meteor::Drawable::Drawable( const Meteor::Drawable& drawable)

: mParent( nullptr), mPosition( drawable.Position()), mAnchorPoint( drawable.AnchorPoint()),
  mSize( drawable.Size()), mTranslation( drawable.mTranslation),
  mScale( drawable.Scale()), mIndexZ( drawable.IndexZ()),
  mRotation( drawable.Rotation()), mAlpha( drawable.Alpha()),
  mVisible( drawable.Visible()), mTweens()
{
}

// -----------------------------------------------------------------------------

const Meteor::Drawable& Meteor::Drawable::operator=( const Meteor::Drawable& rhs)
{
    if ( &rhs == this)
    {
        return *this;
    }

    mPosition = rhs.Position();
    
    mAnchorPoint = rhs.AnchorPoint();
    
    mSize = rhs.Size();
    
    mTranslation = rhs.mTranslation;
    
    mScale = rhs.Scale();
    
    mIndexZ = rhs.IndexZ();
    
    mRotation = rhs.Rotation();
    
    mAlpha = rhs.Alpha();
    
    mVisible = rhs.Visible();
    
    return *this;
}

#pragma mark -
#pragma mark Parent reference
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetParent( Meteor::Drawable* const parent)
{
    if ( parent == nullptr)
    {
        return;
    }

    mParent = parent;
}

// -----------------------------------------------------------------------------

const Meteor::Drawable* const Meteor::Drawable::Parent() const
{
    return mParent;
}


#pragma mark -
#pragma mark Position
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetPosition( const float x, const float y)
{
    mPosition.SetX( x);

    mPosition.SetY( y);
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::SetPosition( const Meteor::Vector2D& newPosition)
{
    if ( mPosition == newPosition )
    {
        return;
    }
    
    mPosition = newPosition;
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::MoveBy( const float x, const float y)
{
    mPosition.SetX( mPosition.X() + x);
    
    mPosition.SetY( mPosition.Y() + y);
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::MoveBy( const Meteor::Vector2D& newPosition)
{
    if ( mPosition == newPosition )
    {
        return;
    }
    
    mPosition.SetX( mPosition.X() + newPosition.X());
    
    mPosition.SetY( mPosition.Y() + newPosition.Y());
}

// -----------------------------------------------------------------------------

const Meteor::Vector2D& Meteor::Drawable::Position() const
{
    return mPosition;
}


#pragma mark -
#pragma mark Anchorpoint
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetAnchorPoint( const float x, const float y)
{
    Meteor::Vector2D newAnchor( x, y);
    
    SetAnchorPoint( newAnchor);
} 

// -----------------------------------------------------------------------------

void Meteor::Drawable::SetAnchorPoint( const Meteor::Vector2D& newAnchor)
{
    if ( mAnchorPoint.X() < 0.0f)
    {
        mAnchorPoint.SetX( 0.0f);
    }
    
    if ( mAnchorPoint.X() > 1.0f)
    {
        mAnchorPoint.SetX( 1.0f);
    }
    
    if ( mAnchorPoint.Y() < 0.0f)
    {
        mAnchorPoint.SetY( 0.0f);
    }
    
    if ( mAnchorPoint.Y() > 1.0f)
    {
        mAnchorPoint.SetY( 1.0f);
    }
    
    const float factorX = ( mAnchorPoint.X() - newAnchor.X());
    
    mTranslation.SetX( mSize.X() * factorX);
    
    const float factorY = ( mAnchorPoint.Y() - newAnchor.Y());
    
    mTranslation.SetY( mSize.Y() * factorY);
    
    mAnchorPoint = newAnchor;
}

// -----------------------------------------------------------------------------

const Meteor::Vector2D& Meteor::Drawable::AnchorPoint() const
{
    return mAnchorPoint;
}

// -----------------------------------------------------------------------------

const Meteor::Vector2D& Meteor::Drawable::Size() const
{
   return mSize;
}


#pragma mark -
#pragma mark Scale
// -----------------------------------------------------------------------------

const float Meteor::Drawable::Scale() const
{
    return mScale;
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::SetScale( const float newScale)
{
    mScale = newScale;
    
    if ( mScale < 0.0f)
    {
        mScale = 0.0f;
    }
}


#pragma mark -
#pragma mark Size
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetSize( const float width, const float height)
{
    Vector2D newSize( width, height);
    
    SetSize( newSize);
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::SetSize( const Meteor::Vector2D& newSize)
{
    mSize = newSize;
    
    if ( mSize.X() < 0.0f)
    {
        mSize.SetX( 0.0f);
    }
    
    if ( mSize.Y() < 0.0f)
    {
        mSize.SetY( 0.0f);
    }
}


#pragma mark -
#pragma mark Rotation
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetRotation( const float angle)
{
    mRotation = angle;
}

// -----------------------------------------------------------------------------

const float Meteor::Drawable::Rotation() const
{
    return mRotation;
}


#pragma mark -
#pragma mark Alpha
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetAlpha( const float alpha)
{
    mAlpha = alpha;
    
    if ( mAlpha > 1.0f)
    {
        mAlpha = 1.0f;
    }
    
    if ( mAlpha < 0.0f)
    {
        mAlpha = 0.0f;
    }
}

// -----------------------------------------------------------------------------

const float Meteor::Drawable::Alpha() const
{
    return mAlpha;
}


#pragma mark -
#pragma mark Z index
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetIndexZ( const int index)
{
    mIndexZ = index;
}

// -----------------------------------------------------------------------------

const int Meteor::Drawable::IndexZ() const
{
    return mIndexZ;
}


#pragma mark -
#pragma mark Visibility
// -----------------------------------------------------------------------------

void Meteor::Drawable::SetVisible( const bool visibility)
{
   mVisible = visibility;
}

// -----------------------------------------------------------------------------

const bool Meteor::Drawable::Visible() const
{
    return mVisible;
}


#pragma mark -
#pragma mark Animation
// -----------------------------------------------------------------------------

void Meteor::Drawable::ApplyTween( Meteor::Tween* tween)
{
    ApplyTween( tween, nullptr);
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::ApplyTween( Meteor::Tween* tween, Meteor::Function* callback)
{
    if ( mTweens.size() < gMaxAnimations)
    {
        std::pair<Tween*, Function*> newTween( tween, callback);
        
        mTweens.push_back( newTween);
    }
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::ApplyTween( Meteor::Tween* tween, const float delay,
                                   Meteor::Function* callback)
{
    Function* delayedCall = new MemberFunction<Meteor::Drawable, Meteor::Tween*>( this, &Drawable::ApplyTween, tween);

    Timer::CallFunctionWithDelay( delayedCall, delay);
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::UpdateTween( const TweenContainerType::iterator& tween,
                                    int* frame)
{
    Tween* currentTween = ( *tween).first;

    switch ( currentTween->Attribute())
    {
        case Tween::Alpha:
            
            SetAlpha( currentTween->Update( *frame).X());
            
            break;
        
        case Tween::Scale:
       
            SetScale( currentTween->Update( *frame).X());
            
            break;
            
         case Tween::Rotation:
            
            SetRotation( currentTween->Update( *frame).X());
            
            break;

        case Tween::Position:
        
            SetPosition( currentTween->Update( *frame));
            
            break;

        default:
            
        break;
    }
    
    ( *frame)++;
    
    if ( currentTween->Finished())
    {
        mTweens.erase( tween);
        
        if ( ( *tween).second)
        {
            (*( *tween).second)();
        }
        
        delete currentTween;
        
        *frame = 0;
    }
}

#pragma mark -
#pragma mark Update/Draw
// -----------------------------------------------------------------------------

void Meteor::Drawable::Update( const float deltaTime)
{
    if ( mTweens.size() == 0)
    {
        return;
    }

    const int size = mTweens.size();

    static std::vector<int> frames = { 0, 0, 0, 0, 0};
    
    TweenContainerType::iterator tween = mTweens.begin();
    
    for ( int i=0; i< size; i++)
    {
        UpdateTween( tween, &frames[i]);
        
        tween++;
    }
}

// -----------------------------------------------------------------------------

void Meteor::Drawable::Draw() const
{
    if ( mVisible == false)
    {
        return;
    }
   
    glTranslatef( mPosition.X(),
                  mPosition.Y(),
                  0.0f);
    
    glRotatef( mRotation, 0.0f, 0.0f, 1.0f);
    
    glTranslatef( mTranslation.X(),
                  mTranslation.Y(),
                  0.0f);
    
    glScalef( mScale, mScale, 1);
    
    glColor4f( 1.0f, 1.0f, 1.0f, mAlpha);
}

